#include <iostream>
#include <cstdlib>
#include <string>

#include <boost/program_options.hpp>
namespace bpo = boost::program_options;

#include "hhvsimple.hpp"
#include "invertimagesimple.hpp"


#include "hhvaligned.hpp"

#ifdef HHV_HAVE_CUDA
#include "hhvgpu.hpp"
#include "invertimagegpu.hpp"
#endif

// ===========================

const char inputTypeDefault = 's';
const char outputTypeDefault = 's';
const char execTypeDefault = 's';

char inputType;
char outputType;
char execType;

std::string inFile( "no input" );
std::string outFile( "no output" );

void ReadCommandLine( int ac, char* av[] ) {

  try {
    bpo::options_description desc( "Allowed options" );

    desc.add_options()
      ("help", "Produce help message")
      ("inputType", bpo::value<char>(&inputType)->default_value(inputTypeDefault),
	   "Type to hold input array"
#ifdef HHV_HAVE_CUDA
		"(s|a|g)"
#else
		"(s|a)"
#endif
		)
      ("outputType", bpo::value<char>(&outputType)->default_value(outputTypeDefault),
	   "Type to hold output array "
#ifdef HHV_HAVE_CUDA
	   "(s|a|g)"
#else
	   "(s|a)"
#endif
	   )	  
      ("execType", bpo::value<char>(&execType)->default_value(execTypeDefault), 
	   "Execution class "
#ifdef HHV_HAVE_CUDA
		"(s|g)"
#else
		"(s)"
#endif
		)
      ;

    bpo::options_description hidden( "Hidden, positional options");
    hidden.add_options()
      ("input", bpo::value<std::string>(&inFile), "Name of the input file")
      ("output", bpo::value<std::string>(&outFile), "Name of the output file" )
      ;

    bpo::positional_options_description posOpt;
    posOpt.add( "input", 1 );
    posOpt.add( "output", 2 );

    bpo::options_description cmdLineOpts;
    cmdLineOpts.add(desc).add(hidden);

    bpo::variables_map vm;
    bpo::store( bpo::command_line_parser( ac, av ).options(cmdLineOpts).positional(posOpt).run(), vm );
    bpo::notify( vm );

    if( vm.count( "help" ) ) {
      std::cout << "ImageInverter [options] <input file> <output file>"
                << std::endl;
      std::cout << desc << std::endl;
      exit( EXIT_SUCCESS );
    }

    if( !vm.count( "input" ) ) {
      std::cerr << "No input file specified!" << std::endl;
      exit( EXIT_FAILURE );
    }

    if( !vm.count( "output" ) ) {
      std::cerr << "No output file specified!" << std::endl;
      exit( EXIT_FAILURE );
    }
  }

  catch( std::exception& e ) {
    std::cerr << "Error: " << e.what() << std::endl;
    exit( EXIT_FAILURE );
  }
  catch( ... ) {
    std::cerr << "Unknown exception" << std::endl;
    exit( EXIT_FAILURE );
  }


  // Do some extra error checking

  switch( inputType ) {
  case 's':
  case 'a':
#ifdef HHV_HAVE_CUDA
  case 'g':
#endif
    break;
  
  default:
    std::cerr << "Unrecognised input type!" << std::endl;
    exit( EXIT_FAILURE );
  }

  
  switch( outputType ) {
  case 's':
  case 'a':
#ifdef HHV_HAVE_CUDA
  case 'g':
#endif
    break;
  
  default:
    std::cerr << "Unrecognised output type!" << std::endl;
    exit( EXIT_FAILURE );
  }

  switch( execType ) {
  case 's':
#ifdef HHV_HAVE_CUDA
  case 'g':
#endif
    break;

  default:
    std::cerr << "Unrecognised execution class!" << std::endl;
    exit( EXIT_FAILURE );
  }

}

// =================================

SciGPU::HHV::HHVolume<long,2>* AllocateImage( const char type ) {

  SciGPU::HHV::HHVolume<long,2> *myImage;

  switch( type ) {
  case 's':
    myImage = new SciGPU::HHV::HHVolumeSimple<long,2>;
    break;

  case 'a':
    myImage = new SciGPU::HHV::HHVolumeAligned<long,2>;
    break;
#ifdef HHV_HAVE_CUDA
  case 'g':
    myImage = new SciGPU::HHV::HHVolumeGPU<long,2>;
    break;
#endif

  default:
    std::cerr << __FUNCTION__
              << ": Unrecognised type " << type << std::endl;
    abort();
  }


  return( myImage );
}



// ===========================

int main( int argc, char *argv[] ) {

  ReadCommandLine( argc, argv );

  SciGPU::HHV::HHVolume<long,2> *input;
  SciGPU::HHV::HHVolume<long,2> *output;
  InvertImage *invertor;

  // --------------------------------

  input = AllocateImage( inputType );
  output = AllocateImage( outputType );

  switch( execType ) {
  case 's':
    invertor = new InvertImageSimple;
    break;

#ifdef HHV_HAVE_CUDA
  case 'g':
    invertor = new InvertImageGPU;
    break;
#endif

  default:
    std::cerr << "Unrecognised execution class!" << std::endl;
    exit( EXIT_FAILURE );
  }

  // --------------------------------

  input->ReadHDF5( inFile );

  invertor->Invert( *output, *input );

  output->WriteHDF5( outFile );
  


  delete input;
  delete output;
  delete invertor;
  
  return( EXIT_SUCCESS );
}
